package com.puzzlebazar.client.puzzle.view;

import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Image;
import com.puzzlebazar.client.puzzle.model.Move;
import com.puzzlebazar.client.puzzle.model.MoveGroup;
import com.puzzlebazar.client.puzzle.model.MoveMessage;

public class HistoryNode extends Image implements MouseDownHandler,
	MouseUpHandler, MouseMoveHandler, HasDoubleClickHandlers {

	private static final String moveUrl = "timeline/circblue.png"; 
	private static final String messageUrl = "timeline/circorange.png"; 
	private static final String movesUrl = "timeline/pointblue.png"; 
	private static final String movemessageUrl = "timeline/pointorange.png"; 
	
	private final Move move;
	private final HistoryNodeCallback callback;
	
	private int startMouseX;
	private boolean retimeNodeAndFollowing;
	private HandlerRegistration mouseMoveHandler = null;
	private int startPosY;
	private int startPosX;
	private int lastPosX;

	
	/**
	 * Creates an image with a specified URL. The load event will be fired once the image
	 * at the given URL has been retrieved by the browser.
	 * 
	 * @param url the URL of the image to be displayed
	 */
	public HistoryNode( Move move, HistoryNodeCallback callback ) {
		super();
		this.move = move;
		this.callback = callback;
		
		if( move instanceof MoveGroup ) {
			if ( ((MoveGroup)move).containMoveMessage() )
				setUrl( movemessageUrl );
			else
				setUrl( movesUrl );
		} else {
			if( move instanceof MoveMessage )
				setUrl( messageUrl );
			else
				setUrl( moveUrl );
		}
		
		addMouseDownHandler( this );
		addMouseUpHandler( this );
	
	}

	@Override
	public void onMouseDown(MouseDownEvent event) {
		if( event.getNativeButton() != NativeEvent.BUTTON_LEFT )
			return;
		if( mouseMoveHandler == null ) {
			DOM.setCapture( this.getElement() );
			retimeNodeAndFollowing = event.isShiftKeyDown();
			startMouseX = event.getClientX();
			AbsolutePanel parent = (AbsolutePanel)getParent();
			startPosX = parent.getWidgetLeft(this);
			startPosY = parent.getWidgetTop(this);
			lastPosX = startPosX;
			mouseMoveHandler = addMouseMoveHandler( this );
		}
		event.preventDefault();
		event.stopPropagation();
	}

	@Override
	public void onMouseUp(MouseUpEvent event) {
		if( event.getNativeButton() != NativeEvent.BUTTON_LEFT )
			return;
		if( mouseMoveHandler != null ) {
			DOM.releaseCapture( this.getElement() );
			mouseMoveHandler.removeHandler();
			mouseMoveHandler = null;
			int posX = getPosXWhenDragging(event.getClientX());			
			if( retimeNodeAndFollowing )
				callback.retimeNodeAndFollowing(this, posX);			
			else
				callback.retimeNode(this, posX);			
		}
		if( lastPosX == startPosX )
			callback.selectNode( this );
		event.preventDefault();
		event.stopPropagation();
	}

	@Override
	public void onMouseMove(MouseMoveEvent event) {
		AbsolutePanel parent = (AbsolutePanel)getParent();
		int posX = getPosXWhenDragging(event.getClientX());
		if( retimeNodeAndFollowing ) {
			posX = callback.clampToValidPosXRetimeNodeAndFollowing(this, posX);
			int diffX = posX - lastPosX;
			int index = parent.getWidgetIndex( this );
			for( int i = index; i < parent.getWidgetCount(); ++i ) {
				Widget widget = parent.getWidget(i);
				parent.setWidgetPosition( widget, 
						parent.getWidgetLeft(widget) + diffX, 
						parent.getWidgetTop(widget) );
			}
		} else {
			posX = callback.clampToValidPosXRetimeNode(this, posX);
			parent.setWidgetPosition( this, posX, startPosY );
		}
		lastPosX = posX;
		event.preventDefault();
		event.stopPropagation();
	}

	/**
	 * Convert the client X position of the mouse to a parent-relative X position.
	 * This call is only valid when dragging the node.
	 * 
	 * @param clientX The client X position to convert
	 * @return The parent-relative X position
	 */
	private int getPosXWhenDragging(int clientX) {
		int diffX = clientX - startMouseX;
		return startPosX + diffX;
	}

	/**
	 * @return The move associated with this history node
	 */
	public Move getMove() {
		return move;
	}

	
	public interface HistoryNodeCallback {
	
		/**
		 * Check if a retime node operation is valid, if not clamp the position to a valid one
		 * 
		 * @param node The node to retime
		 * @param posX The new position of the node, in pixel, relative to the parent container
		 * @return The passed position if it is valid, the clamped version otherwise
		 */
		public int clampToValidPosXRetimeNode( HistoryNode node, int posX );

		/**
		 * Check if a retime node and following operation is valid, if not clamp the position to a valid one
		 * 
		 * @param node The node to retime
		 * @param posX The new position of the node, in pixel, relative to the parent container
		 * @return The passed position if it is valid, the clamped version otherwise
		 */
		public int clampToValidPosXRetimeNodeAndFollowing( HistoryNode node, int posX );
		
		/**
		 * Perform a retime node operation
		 * 
		 * @param node The node to retime
		 * @param posX The new position of the node, in pixel, relative to the parent container
		 */
		public void retimeNode(HistoryNode node, int posX);

		/**
		 * Perform a retime node operation
		 * 
		 * @param node The node to retime
		 * @param posX The new position of the node, in pixel, relative to the parent container
		 */
		public void retimeNodeAndFollowing(HistoryNode node, int posX);

		/**
		 * Select a node
		 * 
		 * @param node The node to select
		 */
		public void selectNode(HistoryNode node);
	}

	@Override
	public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler) {
		return addDomHandler(handler, DoubleClickEvent.getType());
	}
}
